<!DOCTYPE html>
<html lang="cn-zh">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>speed-line</title>
  <style>
    #myCanvas {
      outline: 2px solid;
    }
  </style>
</head>
<body>
  <canvas id="myCanvas" width="500" height="500"></canvas>
</body>
<script>
  const canvas = document.getElementById("myCanvas");
  const ctx = canvas.getContext("2d");

  // 定义矩形的初始位置和速度
  let x = 0;
  let y = 250;
  let dx = 5; // 每帧移动的像素值
  const ad = 2; // 每次移动的间隔帧
  let cd = 0; // 当前移动间隔帧
  let triangles = []; // 三角形数组
  const triangleCount = 96; // 三角形数量

  // 初始化三角形
  function initTriangle() {
    const angleLeft = 180 / Math.PI * Math.atan(canvas.height / canvas.width); // 左直角三角形角度
    const angleTop = 90 - angleLeft; // 上直角三角形角度
    const angleBase = 360 / triangleCount; // 基础角度
    const radianBase = (angleBase * Math.PI) / 180; // 基础弧度

    // 计算左上半边和上左半边三角形的边长并存储
    const lengthLefts = [], lengthTops = [];
    for(let i = 0; i < Math.floor(triangleCount / 8); i++) {
      let otherAngleLeft = angleLeft - angleBase * (i + 1);
      let otherAngleTop = angleTop - angleBase * (i + 1);

      let otherLengthLeft = Math.tan(otherAngleLeft * Math.PI / 180) * canvas.width / 2;
      let lengthLeft = (canvas.height / 2) - (otherLengthLeft > 0 ? otherLengthLeft : 0);
      lengthLefts.push(lengthLeft);

      let otherLengthTop = Math.tan(otherAngleTop * Math.PI / 180) * canvas.height / 2;
      let lengthTop = (canvas.width / 2) - (otherLengthTop > 0 ? otherLengthTop : 0);
      lengthTops.push(lengthTop);
    }

    // 根据计算结果生成三角形
    for(let i = 0; i < 8;i ++) {
      for(let j = 0; j < Math.floor(triangleCount / 8); j++) {
        let ramdomLinePositions = [];
        // 根据不同位置生成三角形
        switch(i) {
          case 0:
            ramdomLinePositions = generateRandomNum(lengthLefts[j - 1] || 0, lengthLefts[j])
            triangles.push({
              x1: canvas.width / 2, y1: canvas.height / 2,
              x2: ramdomLinePositions[0], y2: 0,
              x3: ramdomLinePositions[1], y3: 0,
              color: generateRandomHex(2, true)
            })
            break;
         case 1:
            ramdomLinePositions = generateRandomNum((lengthLefts[j - 1] || 0) + canvas.width / 2, lengthLefts[j] + canvas.width / 2)
            triangles.push({
              x1: canvas.width / 2, y1: canvas.height / 2,
              x2: ramdomLinePositions[0], y2: 0,
              x3: ramdomLinePositions[1], y3: 0,
              color: generateRandomHex(2, true)
            })
            break;
         case 2:
            ramdomLinePositions = generateRandomNum(lengthTops[j - 1] || 0, lengthLefts[j])
            triangles.push({
              x1: canvas.width / 2, y1: canvas.height / 2,
              x2: canvas.width, y2: ramdomLinePositions[0],
              x3: canvas.width, y3: ramdomLinePositions[1],
              color: generateRandomHex(2, true)
            })
            break;
         case 3:
            ramdomLinePositions = generateRandomNum((lengthTops[j - 1] || 0) + canvas.height / 2, lengthLefts[j] + canvas.height / 2)
            triangles.push({
              x1: canvas.width / 2, y1: canvas.height / 2,
              x2: canvas.width, y2: ramdomLinePositions[0],
              x3: canvas.width, y3: ramdomLinePositions[1],
              color: generateRandomHex(2, true)
            })
            break;
         case 4:
            ramdomLinePositions = generateRandomNum(canvas.width - (lengthLefts[j - 1] || 0), canvas.width - lengthLefts[j])
            triangles.push({
              x1: canvas.width / 2, y1: canvas.height / 2,
              x2: ramdomLinePositions[0], y2: canvas.height,
              x3: ramdomLinePositions[1], y3: canvas.height,
              color: generateRandomHex(2, true)
            })
            break;
         case 5:
            ramdomLinePositions = generateRandomNum(canvas.width - ((lengthLefts[j - 1] || 0) + canvas.width / 2), canvas.width - lengthLefts[j] - canvas.width / 2)
            triangles.push({
              x1: canvas.width / 2, y1: canvas.height / 2,
              x2: ramdomLinePositions[0], y2: canvas.height,
              x3: ramdomLinePositions[1], y3: canvas.height,
              color: generateRandomHex(2, true)
            })
            break;
         case 6:
            ramdomLinePositions = generateRandomNum(canvas.height - (lengthTops[j - 1] || 0), canvas.height - lengthTops[j])
            triangles.push({
              x1: canvas.width / 2, y1: canvas.height / 2,
              x2: 0, y2: ramdomLinePositions[0],
              x3: 0, y3: ramdomLinePositions[1],
              color: generateRandomHex(2, true)
            })
            break;
         case 7:
            ramdomLinePositions = generateRandomNum(canvas.height - ((lengthTops[j - 1] || 0) + canvas.height / 2), canvas.height - lengthTops[j] - canvas.height / 2)
            triangles.push({
              x1: canvas.width / 2, y1: canvas.height / 2,
              x2: 0, y2: ramdomLinePositions[0],
              x3: 0, y3: ramdomLinePositions[1],
              color: generateRandomHex(2, true)
            })
            break;
          default:
            break;
        }
      }
    }
  }

  // 动画函数
  function animate() {
    if (cd % ad !== 0) {
      cd ++;
      return;
    }
    triangles.push(triangles.shift());
    // 清除画布
    ctx.clearRect(0, 0, canvas.width, canvas.height);

    // 绘制三角形
    triangles.forEach(e => {
      drawFilledTriangle(e.x1, e.y1, e.x2, e.y2, e.x3, e.y3, e.color);
    })

    // 下一帧
    requestAnimationFrame(animate);
  }

  // 绘制填充的三角形
  function drawFilledTriangle(x1, y1, x2, y2, x3, y3, color) {
    // 绘制三角形路径
    ctx.beginPath();
    ctx.moveTo(x1, y1);
    ctx.lineTo(x2, y2);
    ctx.lineTo(x3, y3);
    ctx.closePath();

    // 设置填充颜色
    ctx.fillStyle = color; // 颜色
    ctx.fill(); // 填充三角形
  }
  
  /**
   * 生成随机十六进制颜色代码
   * 如果isGrayLevelColor为真，生成的将是灰度颜色
   * 
   * @param {number} length - 生成十六进制代码的长度，如果isGrayLevelColor为真，这个长度会翻三倍
   * @param {boolean} isGrayLevelColor - 指定是否生成灰度颜色的标志
   * @return {string} 生成的随机十六进制颜色代码
   */
  function generateRandomHex(length, isGrayLevelColor) {
      let result = '';
      const characters = '0123456789abcdef'; // 16进制字符集
      const charactersLength = characters.length;
  
      // 根据指定的长度生成随机的十六进制字符串
      for (let i = 0; i < length; i++) {
          result += characters.charAt(Math.floor(Math.random() * charactersLength));
      }
  
      // 如果要求生成灰度颜色，则将生成的随机字符串重复三次，分别作为RGB值
      return isGrayLevelColor ? `#${result}${result}${result}` : result;
  }

  /**
   * 生成两个随机数，根据num1和num2的大小关系决定生成的随机数范围和排序方式
   * @param {number} num1 - 第一个数字，用于确定随机数的范围
   * @param {number} num2 - 第二个数字，用于确定随机数的范围
   * @returns {Array} - 包含两个随机数的数组，根据num1和num2的大小关系决定是否排序或逆序
   */
  function generateRandomNum(num1, num2) {
    // 计算两个数字之间的差值，用于确定随机数的范围
    const diff = Math.abs(num1 - num2);
    
    // 如果num1小于num2，生成两个在[num1, num2)范围内的随机数，并按升序排列
    if (num1 < num2) {
      return [
        Math.floor(Math.random() * diff + num1),
        Math.floor(Math.random() * diff + num1),
      ].sort()
    }
    
    // 如果num1大于num2，生成两个在[num2, num1)范围内的随机数，并按降序排列
    else if (num1 > num2) {
      return [
        Math.floor(Math.random() * diff + num2),
        Math.floor(Math.random() * diff + num2),
      ].sort().reverse()
    }
    
    // 如果num1等于num2，直接返回传入的参数，因为没有足够的范围来生成随机数
    else {
      return arguments
    }
  }

  // 初始化三角形
  initTriangle()

  // 循环动画
  let count = 0;
  let addFlag = true;
  const repeatCount = 2;
  const repeatTime = 50;
  /**
   * 动画渲染模式：
   * 0：相邻三角形颜色替换
   * 1：三角形随机颜色
   * 2：三角形重新初始化
  */
  const mode = 2;

  setInterval(() => {
    // 清除画布
    ctx.clearRect(0, 0, canvas.width, canvas.height);

    // 绘制三角形
    if (mode === 0) {
      // 循环改变颜色
      triangles.forEach((e, i) => {
        let colorIndex = count + i
        if (colorIndex >= triangles.length) {
          colorIndex = colorIndex - triangles.length
        }
        drawFilledTriangle(e.x1, e.y1, e.x2, e.y2, e.x3, e.y3, triangles[colorIndex].color);
      })
    } else if (mode === 1) {
      // 重新生成颜色
      triangles.forEach((e, i) => {
        let color = generateRandomHex(2, true);
        e.color = color;
        drawFilledTriangle(e.x1, e.y1, e.x2, e.y2, e.x3, e.y3, e.color);
      })
    } else if (mode === 2) {
      // 重新初始化
      triangles = [];
      initTriangle();
      triangles.forEach((e, i) => {
        drawFilledTriangle(e.x1, e.y1, e.x2, e.y2, e.x3, e.y3, e.color);
      })
    }
    // 循环改变颜色count
    if (count === 0) {
      addFlag = true;
    } else if (count === repeatCount) {
      addFlag = false;
    }
    if (addFlag) {
      count++;
    } else {
      count--;
    }
  }, repeatTime)

  // 开始动画
  // animate();
</script>
</html>